package com.simple.chinagpay.service.impl;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.simple.chinagpay.api.TradeApi;
import com.simple.chinagpay.service.TradeService;
import com.simple.common.entity.constant.Payment;
import com.simple.common.entity.dto.pay.vm.QueryTradeDto;
import com.simple.common.entity.dto.pay.vm.QueryTradeRspDto;
import com.simple.common.entity.dto.pay.vm.RefundTradeDto;
import com.simple.common.entity.dto.pay.vm.RefundTradeRspDto;
import com.simple.common.entity.po.sys.ChannelComp;
import com.simple.common.entity.po.sys.RefundTrade;
import com.simple.common.entity.po.sys.RefundTradeInventory;
import com.simple.common.entity.po.sys.Trade;
import com.simple.common.framework.context.Contexts;
import com.simple.common.framework.retrofit.ExecuteUtils;
import com.simple.common.framework.retrofit.Retrofits;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import retrofit2.Retrofit;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

/**
 * @author yasud
 * @date 2021年03月09日 10:55
 * @description
 */
@Service("TradeServiceImpl2")
public class TradeServiceImpl implements TradeService {

    @Override
    public QueryTradeRspDto query(QueryTradeDto dto) {
        Trade trade = dto.getTrade();
        ChannelComp channel = dto.getChannel();
        String apiHost = channel.getAsString("apiHost");
        Map<String, Object> map = generateQueryParam(trade, channel);
        Retrofit retrofit = Retrofits.SINGLE.createRetrofit(apiHost);
        TradeApi tradeApi = retrofit.create(TradeApi.class);
        Map<String, Object> result = ExecuteUtils.execute(tradeApi.query(map));
        if ("00".equals(result.get("code"))) {
            Map<String, Object> data = (Map<String, Object>) result.get("data");
            Object data1 = result.get("data");
            if (Objects.isNull(data1)) {
                QueryTradeRspDto rspDto = new QueryTradeRspDto();
                trade.setStatus(Trade.Status.FAILED);
                trade.setChannelStatus(Trade.Status.FAILED.name());
                trade.setChannelMsg("订单提交失败");
                trade.setMsg("订单提交失败");
                rspDto.setTrade(trade);
                return rspDto;
            }

            String status = (String) data.get("status");
            if ("1".equals(status)) {
                trade.setStatus(Trade.Status.SUCCESS);
                trade.setMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_SUCCESS_MSG);
                trade.setChannelStatus(status);
                trade.setChannelMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_SUCCESS_MSG);
                trade.setChannelOrderNo((String) data.get("orderId"));
            } else if ("0".equals(status)) {
                trade.setStatus(Trade.Status.PENDING);
                trade.setMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_PENDING_MSG);
                trade.setChannelStatus(status);
                trade.setChannelMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_PENDING_MSG);
                trade.setChannelOrderNo((String) data.get("orderId"));
            } else if ("2".equals(status)) {
                trade.setStatus(Trade.Status.FAILED);
                trade.setMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_FAILURE_MSG);
                trade.setChannelStatus(status);
                trade.setChannelMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_FAILURE_MSG);
                trade.setChannelOrderNo((String) data.get("orderId"));
            }
        } else {
            trade.setStatus(Trade.Status.FAILED);
            trade.setMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_FAILURE_MSG);
            trade.setChannelStatus((String) result.get("code"));
            trade.setChannelMsg(Payment.TRADE_STATUS_DESCRIBE.EVEN_SERIAL_STATUS_PAYMENT_FAILURE_MSG);
            String message = (String) result.get("message");
            if (StringUtils.isNotBlank(message)) {
                trade.setMsg(message);
                trade.setChannelMsg(message);
            }
        }

        QueryTradeRspDto rspDto = new QueryTradeRspDto();
        rspDto.setTrade(trade);
        return rspDto;
    }

    private Map<String, Object> generateQueryParam(Trade trade,
                                                   ChannelComp channel) {
        String secretKey = channel.getAsString("secretKey");
        String mid = channel.getAsString("mid");
        Map<String, Object> param = Maps.newHashMap();

        param.put("merchantId", mid);
        param.put("reqTime", DateFormatUtils.format(new Date(), "yyyyMMddHHmmss"));
        param.put("reqFlowId", RandomStringUtils.randomAlphanumeric(10));
        param.put("orderId", trade.getId() + "");
        param.put("orderTime", DateFormatUtils.format(trade.getCreateTime(), "yyyyMMddHHmmss"));
        param.put("algorithm", "MD5");
        param.put("signature", Mono.fromCallable(() -> {
            TreeMap tm = Maps.newTreeMap();
            tm.putAll(param);
            String s = Contexts.OBJECTMAPPER.writeValueAsString(tm);
            return DigestUtils.md5Hex(s + secretKey);
        }).block());
        return param;
    }

    @Override
    public RefundTradeRspDto refund(RefundTradeDto dto) {
        Trade trade = dto.getTrade();
        RefundTradeInventory inventory = dto.getInventory();
        RefundTrade refundTrade = dto.getRefundTrade();
        ChannelComp channel = dto.getChannel();
        String apiHost = channel.getAsString("apiHost");
        Map<String, Object> map = generateRefundMap(trade, inventory, refundTrade, channel);
        Retrofit retrofit = Retrofits.SINGLE.createRetrofit(apiHost);
        TradeApi tradeApi = retrofit.create(TradeApi.class);
        Map<String, Object> result = ExecuteUtils.execute(tradeApi.refund(map));
        Preconditions.checkState("00".equals(result.get("code")), result.get("message"));
        Map<String, Object> data = (Map<String, Object>) result.get("data");
        inventory.setStatus(RefundTradeInventory.Status.SUCCESS);
        inventory.setChannelRefundStatus((String) result.get("code"));
        inventory.setChannelRefundMsg((String) result.get("code"));
        inventory.setChannelRefundNo((String) data.get("orderId"));
        RefundTradeRspDto rspDto = new RefundTradeRspDto();
        rspDto.setInventory(inventory);
        rspDto.setRefundTrade(dto.getRefundTrade());
        rspDto.setTrade(trade);
        return rspDto;
    }

    private Map<String, Object> generateRefundMap(Trade trade,
                                                  RefundTradeInventory inventory,
                                                  RefundTrade refundTrade,
                                                  ChannelComp channel) {
        String secretKey = channel.getAsString("secretKey");
        String mid = channel.getAsString("mid");
        Map<String, Object> param = Maps.newHashMap();
        param.put("merchantId", mid);
        param.put("flowTime", DateFormatUtils.format(new Date(), "yyyyMMddHHmmss"));
        param.put("flowId", inventory.getId() + "");
        param.put("orderId", trade.getId() + "");
        param.put("orderTime", DateFormatUtils.format(trade.getCreateTime(), "yyyyMMddHHmmss"));
        param.put("currency", refundTrade.getRealRefundCurrency());
        param.put("amount", new BigDecimal(inventory.getRealRefundAmount()).multiply(new BigDecimal("100")).intValue()+"");
        param.put("reason", inventory.getRefundReason());
        param.put("algorithm", "MD5");
        param.put("signature", Mono.fromCallable(() -> {
            TreeMap tm = Maps.newTreeMap();
            tm.putAll(param);
            String s = Contexts.OBJECTMAPPER.writeValueAsString(tm);
            return DigestUtils.md5Hex(s + secretKey);
        }).block());

        return param;
    }
}
